﻿using System.Collections.Generic;
using System.Text;
using FunctionalCLR.Core;

namespace Zove.Runtime
{
    /// <summary>
    /// The runtime factor status
    /// </summary>
    public enum RuntimeFactorStatus
    {
        Input = 0,
        Solved,
        Evaluated,
        Unknown
    }

    /// <summary>
    /// Runtime factor
    /// </summary>
    public class RuntimeFactor
    {
        /// <summary>
        /// The name
        /// </summary>
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// The unit
        /// </summary>
        public string Unit {
            get;
            set;
        }

        /// <summary>
        /// The rule to solved this factor
        /// </summary>
        public RuntimeRule SolvedRule
        {
            get;
            set;
        }

        /// <summary>
        /// The value
        /// </summary>
        public double Value
        {
            get;
            set;
        }

        /// <summary>
        /// The status
        /// </summary>
        public RuntimeFactorStatus Status
        {
            get;
            set;
        }

        /// <summary>
        /// The equations associated with this factor
        /// </summary>
        public List<RuntimeEquation> Equations
        {
            get;
            set;
        }

        /// <summary>
        /// The rules from the equations
        /// </summary>
        public List<RuntimeRule> Rules
        {
            get;
            set;
        }

        /// <summary>
        /// The model to where the factor belongs
        /// </summary>
        public RuntimeModel ParentModel {
            get;
            set;
        }

        /// <summary>
        /// Default constructor
        /// </summary>
        public RuntimeFactor()
        {
            this.Status = RuntimeFactorStatus.Unknown;
            this.Equations = new List<RuntimeEquation>();
            this.Rules = new List<RuntimeRule>();
        }

        /// <summary>
        /// Constructor with a name
        /// </summary>
        /// <param name="factorName"></param>
        public RuntimeFactor(string factorName)
            : this() {
            this.Name = factorName;
        }
        
        /// <summary>
        /// Formulate the evaluating expression of the factor
        /// </summary>
        /// <returns></returns>
        public string Formulate()
        {
            SolutionSimplifier ss = new SolutionSimplifier(this);
            string result = ss.Parse();

            Parser pa 
                = ZoveDependencyManager.Instance.ParserFactory.CreateParser(
                result, this.ParentModel);
            pa.ConstantManager = ZoveDependencyManager.Instance.ConstantManager;
            pa.FunctionManager = ZoveDependencyManager.Instance.FunctionManager;
            Node root = pa.Parse();

            string expr 
                = DefaultDependencyManager.Instance.ExpressionCompiler.Compile(
                root);

            return expr;
        }
    }

    /// <summary>
    /// Runtime equation
    /// </summary>
    public class RuntimeEquation
    {
        /// <summary>
        /// The left hand side expression tree
        /// </summary>
        public Node Left
        {
            get;
            set;
        }

        /// <summary>
        /// The right hand side expression tree
        /// </summary>
        public Node Right
        {
            get;
            set;
        }

        /// <summary>
        /// The equation
        /// </summary>
        public string Equation
        {
            get;
            set;
        }

        /// <summary>
        /// Factors associated in this equation
        /// </summary>
        public List<RuntimeFactor> Factors
        {
            get;
            set;
        }

        /// <summary>
        /// Default constructor
        /// </summary>
        public RuntimeEquation()
        {
            this.Factors = new List<RuntimeFactor>();
        }

        /// <summary>
        /// Constructor with an equation
        /// </summary>
        /// <param name="equation"></param>
        public RuntimeEquation(string equation) : this() {
            this.Equation = equation;
        }
    }

    /// <summary>
    /// Runtime rule
    /// </summary>
    public class RuntimeRule
    {
        /// <summary>
        /// The equation
        /// </summary>
        public RuntimeEquation Equation
        {
            get;
            set;
        }

        /// <summary>
        /// The factor to evaluate
        /// </summary>
        public RuntimeFactor Factor
        {
            get;
            set;
        }

        /// <summary>
        /// The expression tree
        /// </summary>
        public Node Root
        {
            get;
            set;
        }
    }

    /// <summary>
    /// Runtime model
    /// </summary>
    public class RuntimeModel
    {
        /// <summary>
        /// get the number of available factors
        /// </summary>
        public int NumberOfAvailableFactors
        {
            get
            {
                int c = 0;

                foreach (RuntimeFactor rf in this.Factors)
                {
                    if (rf.Status != RuntimeFactorStatus.Unknown)
                    {
                        c++;
                    }
                }

                return c;
            }
        }

        /// <summary>
        /// get the name of the model
        /// </summary>
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// Default constructor
        /// </summary>
        public RuntimeModel()
        {
            this.Factors = new List<RuntimeFactor>();
            this.Equations = new List<RuntimeEquation>();
            this.Rules = new Dictionary<string, RuntimeRule>();
        }

        /// <summary>
        /// The factors
        /// </summary>
        public List<RuntimeFactor> Factors
        {
            get;
            set;
        }

        /// <summary>
        /// The equations
        /// </summary>
        public List<RuntimeEquation> Equations
        {
            get;
            set;
        }

        /// <summary>
        /// Rules with keys of the factors
        /// </summary>
        public Dictionary<string, RuntimeRule> Rules
        {
            get;
            set;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();

            return sb.ToString();
        }

        /// <summary>
        /// Get a factor by its name
        /// </summary>
        /// <param name="factorName">the factor name</param>
        /// <returns>the factor, null if not found</returns>
        public RuntimeFactor GetFactorByName(string factorName)
        {
            foreach (RuntimeFactor rf in this.Factors)
            {
                if (rf.Name == factorName)
                {
                    return rf;
                }
            }

            return null;
        }

        /// <summary>
        /// The instance
        /// </summary>
        public Instance Solution {
            get;
            set;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="re"></param>
        /// <param name="rf"></param>
        /// <returns></returns>
        public static string GetRuleId(RuntimeEquation re, RuntimeFactor rf)
        {
            return re.GetHashCode() + "|" + rf.GetHashCode();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="re"></param>
        /// <param name="rf"></param>
        /// <returns></returns>
        public RuntimeRule GetRuntimeRule(RuntimeEquation re, RuntimeFactor rf)
        {
            string rid = GetRuleId(re, rf);

            return Rules[rid];
        }

        public void Add(RuntimeModel rm)
        {
            this.Equations.AddRange(rm.Equations);

            foreach (RuntimeFactor rf in rm.Factors)
            {
                RuntimeFactor rff = this.GetFactorByName(rf.Name);
                if (rff != null)
                {
                    rff.Equations.AddRange(rf.Equations);
                    rff.Rules.AddRange(rf.Rules);
                }
                else
                {
                    //this.Factors.Add(rf);
                    this.AddFactor(rf);
                }
            }

            foreach (string key in rm.Rules.Keys)
            {
                RuntimeRule rr = rm.Rules[key];
                if (this.Rules.ContainsKey(key))
                {
                    throw new DuplicatedRuleDefinedException(rr);
                }

                this.Rules[key] = rr;
            }
        }

        public RuntimeFactor AddFactor(string factorName) {
            RuntimeFactor rf = new RuntimeFactor();
            rf.Name = factorName;

            return this.AddFactor(rf);
        }

        public RuntimeFactor AddFactor(RuntimeFactor rf) {
            rf.Status = RuntimeFactorStatus.Unknown;
            rf.ParentModel = this;
            this.Factors.Add(rf);

            return rf;
        }
    }
}
